﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SignalR;
using System.Linq;
using Newtonsoft.Json;
using System.Threading;
using SilenthinkCommon.Extension;
using SilenthinkContract.Domain;
using SilenthinkService;
using Web.Components;
using SilenthinkContract;
using Newtonsoft.Json.Converters;
using System.Collections.Concurrent;
using SilenthinkContract.Component;

namespace Web.Areas.Auction.Bid
{
    public sealed class BidNotifier
    {
        private bool updating;
        private static Timer timer;
        private const int UpdateInterval = 1000;
        //private static readonly object TimerLock = new object();
        private static readonly object NotifierLock = new object();
        private readonly static Lazy<BidNotifier> Lazy = new Lazy<BidNotifier>(() => new BidNotifier());

        private static readonly ServiceClientProxy<IAuctionService> AuctionProxy = new ServiceClientProxy<IAuctionService>("AuctionService");

        private readonly ConcurrentDictionary<long, UserInfo> users = new ConcurrentDictionary<long, UserInfo>();
        private readonly ConcurrentDictionary<long, SilenthinkContract.Domain.Auction> auctions = new ConcurrentDictionary<long, SilenthinkContract.Domain.Auction>();
        public static BidNotifier Instance
        {
            get
            {
                return Lazy.Value;
            }
        }

        private BidNotifier()
        {
            LoadDefaultAuctions();
            Start();
        }

        public void Start()
        {
            timer = new Timer(UpdateAuction, null, UpdateInterval, UpdateInterval);
        }

        public bool Stop()
        {
            if (timer != null)
            {
                timer.Dispose();
            }
            return true;
        }

        public SkResult<Bidding> GetResult(long userId, string userName, string nickName, string ip, long auctionId)
        {
            var result = new SkResult<Bidding>(false);
            var auction = GetAuction(auctionId);
            if (auction.Status == AuctionStatus.Dealt)
            {
                result.Message = "该拍品已成交";
                return result;
            }
            if (userId == auction.CurrentUserId)
            {
                result.Message = "不可连续出价";
                return result;
            }
            if (DateTime.Now < auction.StartDateTime)
            {
                result.Message = "不在竞拍时间内";
                return result;
            }
            var user = GetUserInfo(userId);
            if (user.Amount < auction.PerPrice)
            {
                result.Message = "余额不足";
                return result;
            }
            return Bid(ref user, ref auction, userName, nickName, ip);
        }

        public IList<SilenthinkContract.Domain.Auction> GetCurrentAuctionInfo()
        {
            return auctions.Values.ToList();
        }

        private UserInfo GetUserInfo(long userId)
        {
            if (users.ContainsKey(userId))
            {
                return users[userId];
            }
            var user = GetUser(userId);
            lock (NotifierLock)
            {
                users[userId] = user;
            }

            return users[userId];
        }

        private SilenthinkContract.Domain.Auction GetAuction(long auctionId)
        {
            if (auctions.ContainsKey(auctionId))
            {
                return auctions[auctionId];
            }
            //var auction = new SilenthinkContract.Domain.Auction() { Id = 1, CurrentPrice = 23.00M, Price = 2, Interval = 40, OnlineTime = new DateTime(2012, 7, 24), BeginTime = new DateTime(2012, 7, 24) };

            var auction = AuctionProxy.Invoke(o => o.GetAuction(auctionId));
            lock (NotifierLock)
            {
                auctions[auctionId] = auction;
            }
            return auctions[auctionId];
        }

        private SkResult<Bidding> Bid(ref UserInfo user, ref SilenthinkContract.Domain.Auction auction, string userName, string nickName, string ip)
        {
            var bidding = new Bidding();
            var dateTime = DateTime.Now;
            DateTime expectedTime;
            var result = new SkResult<Bidding>(true, string.Empty, null);
            lock (NotifierLock)
            {
                var amount = user.Amount - auction.PerPrice;
                user.Amount = amount;

                var price = auction.CurrentPrice + 0.01M;

                var second = auction.Interval;
                expectedTime = dateTime.AddSeconds(second);

                auction.CurrentPrice = price;
                auction.CurrentUserId = user.Id;
                auction.CurrentNickName = nickName;
                auction.ExpectedTime = expectedTime;

                users[user.Id] = user;
                auctions[auction.Id] = auction;
            }
            bidding.Price = auction.PerPrice;
            bidding.AuctionId = auction.Id;
            bidding.CurrentPrice = auction.CurrentPrice;
            bidding.UserId = auction.CurrentUserId;
            bidding.NickName = auction.CurrentNickName;
            bidding.CreatedIp = ip;
            bidding.CreatedBy = userName;
            bidding.CreatedOn = dateTime;

            result.Data = bidding;
            //new AuctionService().CreateBidding(bidding);
            //Task.Factory.StartNew(() => AuctionProxy.Invoke(o => o.CreateBidding(bidding)));

            return result;
        }

        private void UpdateAuction(object state)
        {
            if (updating)
            {
                return;
            }

            lock (NotifierLock)
            {
                if (!updating)
                {
                    updating = true;

                    foreach (var auction in auctions.Values.ToList().Where(o => o.Status == 0))
                    {
                        if (DateTime.Now >= auction.ExpectedTime)
                        {
                            auction.Status = AuctionStatus.Dealt;
                            auctions[auction.Id] = auction;
                            //Broadcast(auction);
                        }
                    }
                    updating = false;
                }
            }
        }

        private static void Broadcast(Bidding bid)
        {
            var result = new SkResult<Bidding>(true, string.Empty, bid);
            var json = JsonConvert.SerializeObject(result, new JavaScriptDateTimeConverter());
            GetConnection().Broadcast(json);
        }

        private static IConnection GetConnection()
        {
            return GlobalHost.ConnectionManager.GetConnectionContext<BidConnection>().Connection;
        }

        private static UserInfo GetUser(long userId)
        {
            var user = new UserInfo();
            if (userId == 1)
            {
                user.Id = 1;
                user.Amount = 300;
            }
            if (userId == 8)
            {
                user.Id = 8;
                user.Amount = 400;
            }
            return user;
        }

        private void LoadDefaultAuctions()
        {
            var data = AuctionProxy.Invoke(o => o.GetPagedAuction(1, 50)).Data;
            data.ForEach(action => auctions.TryAdd(action.Id, action));
        }
    }
}